/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.port;

import it.free.i.management.JmxFacilities;
import it.free.i.virtual.machine.hal.DeviceAliasRuntimeException;
import it.free.i.virtual.machine.hal.LinkingOperationException;
import it.free.i.virtual.machine.hal.devicealias.DeviceAliasFacilities;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommand;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommandVerb;
import it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.PositioningSystemDomainObjectFactory;
import it.free.i.virtual.machine.hal.devicealias.echo.ActuatorInvocationFeedBackEcho;
import it.free.i.virtual.machine.hal.devicealias.echo.ActuatorInvocationFeedBackEchoImpl;
import it.free.i.virtual.machine.hal.devicealias.echo.PingEcho;
import it.free.i.virtual.machine.hal.devicealias.echo.PingEchoImpl;
import it.free.i.virtual.machine.hal.devicealias.echo.PositionEcho;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.jbullet.VehicleControls;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPort;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPortSupportImpl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Properties;

import javabullet.dynamics.RigidBody;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.Notification;
import javax.management.ReflectionException;

import org.apache.log4j.Logger;
import org.lwjgl.input.Keyboard;
  
/**
 * Dummy port implementation. A device port alias port implementations can receive notifications, have method
 * executed via a jmx call, send notifications about unsolicited read actions that the port want to forward to the
 * controller
 * @author stefano
 */
public class PositioningSystemCoordinateReaderPortImpl extends DeviceAliasPortSupportImpl implements DeviceAliasPort {

	private long sequenceNumber = 0;
	private Long almostDummyName = 0L;
	private static Logger logger = null;
	private static final String DUMMY_NAME = "devicealias.port.positioningsystem.locator";
	private VehicleControls myVehicleControls = null;
	
	private RigidBody myChassis = null;
	
	static{
		logger = Logger.getLogger(PositioningSystemCoordinateReaderPortImpl.class);
	}
	
	public PositioningSystemCoordinateReaderPortImpl(RigidBody chassis, VehicleControls vehicleControls){
		almostDummyName = System.currentTimeMillis();
		this.myChassis = chassis;
		this.myVehicleControls = vehicleControls;
	}
	
	public DeviceAliasPortCommandVerb getMode() {
		return DeviceAliasPortCommandVerb.PORT_DO_READ;
	}

	public String getName() {
		return DUMMY_NAME + "-" + almostDummyName;
	}

	public Object invoke(List<Object> input) {
		//logger.info("# device alias port [" + this.getName() + "] [invoke] call");
		return null;
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void reset() throws DeviceAliasRuntimeException {
		
		logger.info("#Device alias reset invoked");
	}

	public void setMode(DeviceAliasPortCommandVerb mode) {
	}

	

	public void link(Properties virtualDeviceConfiguration) throws LinkingOperationException {
		logger.info("# device alias port [" + this.getName() + "] [link] call");
	}

	public void unlink() throws LinkingOperationException {
		logger.info("# device alias port [" + this.getName() + "] [unlink] call");
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void handleNotification(Notification notification, Object handback) {
		
		boolean isValid = DeviceAliasFacilities.isPortCommandValid(notification);
		
		if(!isValid){
		   logger.error("Notification received is not valid");
		}
		
		//logger.info("# device alias port [" + this.getName() + "] [command received], now digesting");
		DeviceAliasPortCommand deviceAliasPortCommand = (DeviceAliasPortCommand) notification.getUserData();
		//Execute the command switch case and then decide how to act
		digestCommand(deviceAliasPortCommand);
		
	}
	
	/**
	 * Process the command received
	 * @param command is the device alias command to digest
	 */
	private void digestCommand(DeviceAliasPortCommand command){
	
		DeviceAliasPortCommandVerb verb = command.getVerb();
		
		//logger.info("Device alias [" + this.getName() + "] COMMAND VERB RECEIVED: " + verb);
		
		if(DeviceAliasPortCommandVerb.PORT_DO_READ == verb){
		   //logger.info("<-- x READ Request received");
		   if(this.myChassis != null){
			  
			  PositionEcho positionEcho = PositioningSystemDomainObjectFactory.buildPositionEcho(this.myChassis.getCenterOfMassPosition().x,
						 															 			 this.myChassis.getCenterOfMassPosition().y,
						 															 			 this.myChassis.getCenterOfMassPosition().z,
						 															 			 this.myChassis.getLinearVelocity().x,
						 															 			 this.myChassis.getLinearVelocity().y,
						 															 			 this.myChassis.getLinearVelocity().z);
			  
			  positionEcho.setInitiaLDeviceAliasPortCommand(command);
			  
			  Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.PORT_DO_READ_ANSWER,
					   											  this.getName() + " READ verb answer ", 
					   											  sequenceNumber++,
					   											  positionEcho);
			  sendNotification(notification);
			  //logger.info("--> x READ Response sent: [" + positionEcho + "]");
		   }else{
			     logger.error("--> x READ Response NO DATA AVAILABLE YET");
		   }
		   
		}else if(DeviceAliasPortCommandVerb.PORT_PING == verb){
			     //logger.info("<-- x PING Request received");
				 PingEcho pingEcho = new PingEchoImpl();
				 pingEcho.setEchoValue(1);
				 
				 Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.PORT_PING_ECHO,
						 									         this.getName() + " PING verb answer " + System.currentTimeMillis(),
						 									         sequenceNumber++,
						 									         pingEcho);
				 sendNotification(notification);
				 //logger.info("--> x PING Echo sent");
		}else if(DeviceAliasPortCommandVerb.PORT_DRIVE_ACCELERATE == verb){
				 logger.info("Driving: accellerate");
			     this.myVehicleControls.specialKeyboard(200, 0, 0);
			     try{
					Thread.sleep(400);
				 }catch(InterruptedException exc){
					    exc.printStackTrace();
				 }
			     this.myVehicleControls.specialKeyboardUp(200, 0, 0);
			     
			     fireFeedbackNotifications(DeviceAliasPortCommandVerb.PORT_DRIVE_ACCELERATE_FEEDBACK, command);
		
		}else if(DeviceAliasPortCommandVerb.PORT_DRIVE_BRAKE == verb){
				 logger.info("Driving: stop");
			     this.myVehicleControls.specialKeyboard(Keyboard.KEY_DOWN, 0, 0);
			     try{
				     Thread.sleep(400);
				 }catch(InterruptedException exc){
						exc.printStackTrace();
				 }
			     this.myVehicleControls.specialKeyboardUp(Keyboard.KEY_DOWN, 0, 0);
			     
			     fireFeedbackNotifications(DeviceAliasPortCommandVerb.PORT_DRIVE_BRAKE_FEEDBACK, command);
			     
		}else if(DeviceAliasPortCommandVerb.PORT_DRIVE_LEFT == verb){
				 logger.info("Driving: go left");
			     this.myVehicleControls.specialKeyboard(Keyboard.KEY_LEFT, 0, 0);
			     
			     fireFeedbackNotifications(DeviceAliasPortCommandVerb.PORT_DRIVE_LEFT_FEEDBACK, command);
			     
		}else if(DeviceAliasPortCommandVerb.PORT_DRIVE_RIGHT == verb){
			     
				 logger.info("Driving: go right");
			     this.myVehicleControls.specialKeyboard(Keyboard.KEY_RIGHT, 0, 0);
			     
			     fireFeedbackNotifications(DeviceAliasPortCommandVerb.PORT_DRIVE_RIGHT_FEEDBACK, command);
			     
		}else{
			  logger.error("Unknown command received: [" + verb + "]");
		}
	}

	/**
	 * Upon every actuator's invocation, this method will be used to send back a notification to say that 
	 * operation was performed
	 */
	private final void fireFeedbackNotifications(DeviceAliasPortCommandVerb deviceAliasPortCommandVerb, DeviceAliasPortCommand command){
		
		ActuatorInvocationFeedBackEcho actuatorInvocationFeedBackEcho = new ActuatorInvocationFeedBackEchoImpl();
		//This feedback save the command for the virtual actuator invocation, so that the swarm intelligence
		// will be able to invoke it again
		actuatorInvocationFeedBackEcho.setActuatorInvocation(command);
		
		if(deviceAliasPortCommandVerb == DeviceAliasPortCommandVerb.PORT_DRIVE_ACCELERATE_FEEDBACK){
		
			actuatorInvocationFeedBackEcho.setInvokedActuatorName("THROTTLE"); //To increase vehicle velocity
			actuatorInvocationFeedBackEcho.setValueWrittenOnActuator(0);
			
		}else if(deviceAliasPortCommandVerb == DeviceAliasPortCommandVerb.PORT_DRIVE_RIGHT_FEEDBACK){
			
			     actuatorInvocationFeedBackEcho.setInvokedActuatorName("DRIVE_RIGHT");
			     actuatorInvocationFeedBackEcho.setValueWrittenOnActuator(0);
		}else if(deviceAliasPortCommandVerb == DeviceAliasPortCommandVerb.PORT_DRIVE_LEFT_FEEDBACK){
			
			     actuatorInvocationFeedBackEcho.setInvokedActuatorName("DRIVE_LEFT");
			     actuatorInvocationFeedBackEcho.setValueWrittenOnActuator(0);
		}else if(deviceAliasPortCommandVerb == DeviceAliasPortCommandVerb.PORT_DRIVE_BRAKE_FEEDBACK){
			
			     actuatorInvocationFeedBackEcho.setInvokedActuatorName("BRAKE");
			     actuatorInvocationFeedBackEcho.setValueWrittenOnActuator(0);
		}
		
		
		Notification notification = super.buildNotification(deviceAliasPortCommandVerb, 
															this.getName(), 
															sequenceNumber++,
															actuatorInvocationFeedBackEcho);

		super.sendNotification(notification);
	}

	@Override
	public Object getAttribute(String attribute)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {
		return null;
	}



	@Override
	public AttributeList getAttributes(String[] attributes) {
		return null;
	}



	@Override
	public MBeanInfo getMBeanInfo() {
		return JmxFacilities.buildMBeanInfo(this.getClass(), this.getName());
	}



	@Override
	public Object invoke(String actionName, 
						 Object[] params, 
						 String[] signature) throws MBeanException, ReflectionException {
			
		Object invocationReturnValue = null;
	    
		try{
            Method method = this.getClass().getMethod(actionName, new Class[params.length]);
            invocationReturnValue = method.invoke(this, params);
            logger.info("Invoked action [" + actionName + "] invoked method [" + method.getName() + "]");
        }catch(NoSuchMethodException nsmExc) {    
               throw (new MBeanException(nsmExc));
        }catch(SecurityException sExc) {
               throw (new MBeanException(sExc));
        }catch(IllegalArgumentException iaExc) {
               throw (new MBeanException(iaExc));
        }catch(IllegalAccessException iaExc) {
               throw (new MBeanException(iaExc));    
        }catch(InvocationTargetException itExc) {
               throw (new MBeanException(itExc));
        }
        
        return invocationReturnValue;
	}



	@Override
	public void run() {}



	@Override
	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {
	}



	@Override
	public AttributeList setAttributes(AttributeList attributes) {
		
		return null;
	}
}
